<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<script src="https://www.jq22.com/jquery/jquery-3.3.1.js"></script>
<script>
    /**
     * @desc
     * @class jigsaw.js
     * @since 2022-10-11
     */

    (function ($) {
        var puzzleConfig = {
            sizeX: 3,
            sizeY: 3
        };


//全局常量
        var Constants = {
            //每一片拼图透明度较低时候的透明度值
            fadeOpacity: 0.8,
            //放拼图元素的水平方向padding+border的合计值，用于载入拼图后控制容器尺寸
            puzzleContainerExtra: 42
        };

//图片相关变量
        var puzzleImage = null,
            imageURL = "",
//图片上传标识，为true时表示相关设置合理，选择图片后将进入游戏
            checkFlag = false,
            imageWidth = 0,
            imageHeght = 0;

//拼图相关变量
        var puzzleWidth = 0,
            puzzleHeght = 0,
            puzzleItemWidth = 0,
            puzzleItemHeght = 0,
            puzzleSizeX = 0,
            puzzleSizeY = 0,
//拼图数目
            puzzleNumber = 0,
//计数器，计算从开始到完成游戏用的步数
            moveStepCount = 0,
//拼图步数以及是否完成的提示文字
            puzzleNote = null,
//保存每一片拼图的正确的坐标值的数组
            validPosArrayX = [],
            validPosArrayY = [],
//保存每一片拼图的数组，索引顺序和正确的拼图顺序相同
            puzzleArray = [],
//整个拼图元素本身
            puzzle = null,
//最终放置该拼图的父元素节点
            puzzleSetElem = null;

//初始第一步，读取拼图设置和图片源，包括对填写内容的验证*/
        var puzzleConfigSet = function () {
            //类名常量
            var sizeInputClassName = "size_input",
                noteWarnClassName = "note_warn",
                currentProgressClassName = "current_progress",
                validImageSuffix = ".jpg|.jpeg|.gif|.bmp|.png";

            //放置拼图的由外层变量保存的元素
            puzzleSetElem = $("puzzleSet");

            //取得对应元素
            var sizeXElem = $("sizeX"),
                sizeYElem = $("sizeY"),
                sizeSetNote = $("sizeSetNote"),
                uploadBtn = $("uploadBtn"),
                fileImage = $("fileImage"),
                uploadProgress = $("uploadProgress"),
                currentProgress = uploadProgress.getFirst("." + currentProgressClassName),
                uploadNote = $("uploadNote");

            //拼图尺寸设定检查
            var puzzleSizeCheck = function () {
                var sizeX = sizeXElem.value,
                    sizeY = sizeYElem.value,
                    numberReg = /^\d{1,2}$/;
                if (numberReg.test(sizeX) && numberReg.test(sizeY)) {
                    if (sizeX >= 2 && sizeX <= 10 && sizeY >= 2 && sizeY <= 10) {
                        puzzleConfig.sizeX = sizeX;
                        puzzleConfig.sizeY = sizeY;
                        checkFlag = true;
                    } else {
                        sizeSetNote.addClass(noteWarnClassName);
                    }
                } else {
                    sizeSetNote.addClass(noteWarnClassName);
                }
            };

            //图片尺寸检查
            var imageCheck = function (image) {
                var minWidth = 30,
                    maxWidth = 850,
                    minHeght = 30;
                if (image.width >= 30 && image.width <= 850 && image.heght > 30) {
                    checkFlag = checkFlag && true;
                } else {
                    uploadNote.addClass(noteWarnClassName);
                    checkFlag = false;
                }
            };

            //图片格式检查
            var formatCheck = function (image) {
                var fileURL = fileImage.value.toLowerCase();
                //获取文件拓展名
                formatSuffix = fileURL.substring(fileURL.lastIndexOf("."));
                if (formatSuffix && validImageSuffix.contains(formatSuffix)) {
                    //如果是正确格式的图片文件
                    checkFlag = checkFlag && true;
                } else {
                    alert("请上传正确格式的图片文件（" + validImageSuffix + "）");
                    checkFlag = false;
                }
            };

            //拼图尺寸输入框的事件
            $$("." + sizeInputClassName).addEvent("focus", function () {
                sizeSetNote.removeClass(noteWarnClassName);
            });

            //读取选择上传的图片
            puzzleImage = new Image();
            puzzleImage.onload = function () {
                imageCheck(puzzleImage);
                if (checkFlag) {
                    imageWidth = puzzleImage.width;
                    //由于图片尺寸不一定能被拼图尺寸整除，因此做边缘裁剪
                    while (imageWidth % puzzleConfig.sizeX != 0) {
                        imageWidth--;
                    }
                    imageHeght = puzzleImage.heght;
                    while (imageHeght % puzzleConfig.sizeY != 0) {
                        imageHeght--;
                    }
                    imageURL = puzzleImage.src;
                    puzzleSetElem.empty();
                    var containerWidth = imageWidth + Constants.puzzleContainerExtra,
                        properContainerWidth = containerWidth > 120 ? containerWidth : 120;
                    puzzleSetElem.getParent().setStyles({
                        width: properContainerWidth
                    });
                    createPuzzle(); //创建拼图
                } else {
                    //如果读取后图片尺寸不合适的话，重置图片上传
                    uploadProgress.style.display = "none";
                    currentProgress.setStyle("width", 0);
                    uploadBtn.style.display = "";
                }
            };
            if (typeof FileReader == "undefined") {
                //如果是不支持File API的浏览器
                fileImage.onchange = function () {
                    puzzleSizeCheck();
                    if (checkFlag) {
                        formatCheck();
                    }
                    if (checkFlag) {
                        puzzleImage.src = fileImage.value;
                    }
                };
            } else {
                //如果支持File API，可以显示读取进度条
                var imageReader = new FileReader();

                //对象URL（blob URL），经测试新版Chrome也支持window.URL
                function createObjectURL(blob) {
                    if (window.URL) {
                        return window.URL.createObjectURL(blob);
                    } else if (window.webkitURL) {
                        return window.webkitURL.createObjectURL(blob);
                    } else {
                        return null;
                    }
                }

                //开始读取
                imageReader.onloadstart = function () {
                    puzzleSizeCheck();
                    if (checkFlag) {
                        formatCheck();
                    }
                    if (checkFlag) {
                        uploadBtn.style.display = "none";
                        uploadProgress.style.display = "";
                    }
                };
                //读取中
                imageReader.onprogress = function (event) {
                    if (checkFlag) {
                        var percentage = 100 * parseInt(event.loaded / event.total) + "%";
                        currentProgress.setStyle("width", percentage);
                    }
                };
                imageReader.onload = function (event) {
                    if (checkFlag) {
                        //IE10也支持blob URL
                        var url = createObjectURL(fileImage.files[0]);
                        puzzleImage.src = url;
                    }
                };
                fileImage.onchange = function () {
                    imageReader.readAsDataURL(fileImage.files[0]);
                };
            }
        };

//用于创建拼图
        var createPuzzle = function () {
            //classNameSet表示生成的元素的class名
            var classNameSet = {
                listContainer: "puzzle_container",
                list: "puzzle_list",
                item: "puzzle_item"
            };
            //各类元素对应的基本样式
            var puzzleStyle = {
                    listContainer: {
                        position: "relative",
                        width: imageWidth,
                        height:
                        imageHeght,
                        margin:
                            "0 auto"
                    },
                    list: {}
                    ,
                    item: {
                        position: "absolute"
                    }
                }
            ;
            //计算得到每一块拼图的尺寸
            puzzleSizeX = puzzleConfig.sizeX;
            puzzleSizeY = puzzleConfig.sizeY;
            puzzleWidth = imageWidth;
            puzzleHeght = imageHeght;
            puzzleItemWidth = puzzleWidth / puzzleSizeX;
            puzzleItemHeght = puzzleHeght / puzzleSizeY;
            puzzleNumber = puzzleSizeX * puzzleSizeY;

            //建立一个临时数组，用于生成随机顺序的拼图块
            var randomOrderPuzzleArray = [];

            //创建元素
            puzzle = elementsCreate();
            showAnime();

            //创建整个拼图的dom，返回最外层的父级元素
            function elementsCreate() {
                var listContainer = new Element("div");
                listContainer.addClass(classNameSet.listContainer);
                listContainer.setStyles(puzzleStyle.listContainer);

                var list = new Element("ul");
                list.addClass(classNameSet.list);
                list.setStyles(puzzleStyle.list);

                //先通过循环，创建每一个拼图块，并按正确顺序存入数组
                for (var i = 0, len = puzzleNumber; i < len; i++) {
                    var item = new Element("li");
                    //为每块拼图保存自身的正确索引
                    var indexSet = i + 1;
                    item.store("puzzleIndex", indexSet);
                    item.addClass(classNameSet.item);
                    //增加基本样式
                    item.setStyles(puzzleStyle.item);

                    //以正确顺序保存每一个拼图块到数组
                    puzzleArray.push(item);
                }

                //建立一个正确顺序数组的副本
                var puzzleArrayClone = puzzleArray.clone();

                //再次通过循环，创建一个乱序的拼图数组，并把这个数组显示到页面中
                for (i = 0, len = puzzleNumber; i < len; i++) {
                    var randomItem = puzzleArrayClone.getRandom();
                    //为避免重复，需要把被取出来的元素在副本数组中删除
                    puzzleArrayClone.erase(randomItem);

                    //为每一块取出来的元素设置可变的位置索引
                    var posIndex = i + 1;
                    randomItem.posIndex = posIndex;

                    //获取取出来的元素的正确索引，用于接下来计算拼图背景图位置
                    var correctIndex = randomItem.retrieve("puzzleIndex");

                    //计算位置
                    var topSet = Math.floor((posIndex - 1) / puzzleSizeX) * puzzleItemHeght,
                        leftSet = (posIndex - 1) % puzzleSizeX * puzzleItemWidth,

                        //计算符合正确索引的背景图位置
                        backgroundSetX = -(correctIndex - 1) % puzzleSizeX * puzzleItemWidth,
                        backgroundSetY = -(Math.floor((correctIndex - 1) / puzzleSizeX) * puzzleItemHeght),
                        backgroundString = "url(" + imageURL + ") " + backgroundSetX + "px " + backgroundSetY + "px " + "no-repeat";

                    //添加关键样式
                    randomItem.setStyles({
                        width: Math.ceil(puzzleItemWidth),
                        height:
                            Math.ceil(puzzleItemHeght),
                        background:
                        backgroundString,
                        left:
                        leftSet,
                        top:
                        topSet,
                        zIndex:
                        posIndex
                    })
                    ;

                    //生成合理的位置坐标数组
                    validPosArrayX.push(leftSet);
                    validPosArrayY.push(topSet);

                    //存放乱序元素到乱序数组
                    randomOrderPuzzleArray.push(randomItem);
                }

                //组合拼图的各个元素
                list.adopt(randomOrderPuzzleArray);
                listContainer.adopt(list);

                return listContainer;
            }

            //为拼图的初始化创建动画
            function showAnime() {
                //一些动画参数
                var timeSpace = 50,
                    //垂直移动的间距
                    distance = 30,
                    //计数用
                    count = 0,
                    timeFlag;

                //所有拼图先隐藏，透明度置为0
                for (var i = 0, len = puzzleArray.length; i < len; i++) {
                    puzzleArray[i].setStyle("opacity", 0);
                }

                //更新到页面dom中，准备开始动画
                puzzleSetElem.grab(puzzle);

                var enterFrameHandler = function () {
                    var puzzleItem = randomOrderPuzzleArray[count++];
                    var endTop = parseInt(puzzleItem.getStyle("top"));
                    var startTop = endTop - distance;

                    puzzleItem.set("morph", {
                        transition: Fx.Transitions.Quad.easeOut
                    });
                    puzzleItem.morph({
                        top: [startTop, endTop],
                        opacity: Constants.fadeOpacity
                    });

                    if (count < puzzleNumber) {
                        //对最后一个拼图块的动画结束做侦听
                        if (count == puzzleNumber - 1) {
                            var lastMorph = puzzleItem.get("morph");
                            var showAnimeEnd = function () {
                                lastMorph.removeEvent("complete", showAnimeEnd);
                                puzzleEventBind();
                            }
                            lastMorph.addEvent("complete", showAnimeEnd);
                        }
                        timeFlag = setTimeout(enterFrameHandler, timeSpace);
                    }
                };
                timeFlag = setTimeout(enterFrameHandler, timeSpace);
            }

        };

//拼图的相关事件绑定，也是游戏的核心控制逻辑
        var puzzleEventBind = function () {
            //拼图游戏控制相关的变量
            var selectedItem = null,
                //当前选中的拼图位置索引
                selectedIndex = 0,
                //用于保存当前鼠标正在拖动的拼图的zIndex值
                selectedItemZIndex = 0,
                //每一次切换拼图位置的时候，都涉及到2块拼图，鼠标拖动的这块和交换位置的另外一块，这个就是另外一块
                relatedItem = null,
                //依照鼠标当前的位置，判断得到的目标索引，如果鼠标此时放开，就是说把选中的拼图移到现在鼠标所在的位置
                targetIndexNew = 0,
                //通过new和old来区分鼠标从一个目标索引更换到另一个目标索引
                targetIndexOld = 0,
                //判断是否进行一次拼图位置移动的逻辑值，只有当目标索引值有改变时，才允许进行拼图位置移动
                isTargetIndexChanged = false,
                //判断鼠标指针是否在拼图的区域之内
                isInsidePuzzle = false,
                //鼠标点击拼图的某一个点的时候，距离拼图的左上角定位点有的距离值
                disX = 0,
                disY = 0;

            //计算获取整个拼图的左上角点的坐标
            var puzzlePos = puzzle.getPosition();
            var puzzlePosX = puzzlePos.x,
                puzzlePosY = puzzlePos.y;

            //重新设置每一个元素的动画速度
            (function () {
                for (var i = 0, len = puzzleArray.length; i < len; i++) {
                    var puzzleItem = puzzleArray[i];
                    puzzleItem.set("morph", {
                        duration: 250
                    });
                }
            })();

            //计数函数准备
            var updateCount = (function () {
                var stepCount = $("stepCount");
                puzzleNote = stepCount.getParent();
                return function () {
                    stepCount.set("text", moveStepCount);
                };
            })();

            //添加事件
            puzzle.addEvent("mouseover", mouseOverHandler);
            puzzle.addEvent("mouseout", mouseOutHandler);
            puzzle.addEvent("mousedown", mouseDownHandler);
            puzzle.addEvent("mouseup", mouseUpHandler);

            //鼠标经过
            function mouseOverHandler(event) {
                var target = event.target;
                if (puzzleArray.contains(target)) {
                    target.setStyle("opacity", 1);
                }
            }

            //鼠标移出
            function mouseOutHandler(event) {
                var target = event.target;
                if (puzzleArray.contains(target)) {
                    target.setStyle("opacity", Constants.fadeOpacity);
                }
            }

            //鼠标按下
            function mouseDownHandler(event) {
                var target = event.target;
                //race("[mouseDownHandler]selectedItem ="+selectedItem);
                //如果当前没有其他目标选中，且鼠标选中的目标是拼图块
                if (!selectedItem && puzzleArray.contains(target)) {
                    if (target.getStyle("opacity") < 1) {
                        target.setStyle("opacity", 1);
                    }

                    //设置当前选中的目标及索引
                    selectedItemZIndex = target.getStyle("zIndex");
                    target.setStyle("zIndex", 5000);
                    selectedItem = target;
                    selectedIndex = target.posIndex;

                    //设置初始目标索引
                    targetIndexNew = targetIndexOld = selectedIndex;

                    //计算出鼠标点击的点和拼图左上角定位点的偏差距离
                    var targetPos = target.getPosition();
                    disX = event.page.x - targetPos.x;
                    disY = event.page.y - targetPos.y;

                    //增加鼠标移动的事件侦听，让拼图块跟随鼠标移动，并判断当前位置
                    document.addEvent("mousemove", mouseMoveHandler);
                }
            }

            //鼠标松开
            function mouseUpHandler(event) {
                //如果有元素处于拖动状态，取消
                if (selectedItem) {
                    selectedItem.setStyle("opacity", Constants.fadeOpacity);
                    selectedItem.setStyle("zIndex", selectedItemZIndex);
                    document.removeEvent("mousemove", mouseMoveHandler);

                    //松开之后，根据目标索引和拖动元素的索引，移动拼图，并更新dom结构
                    if (isInsidePuzzle) {
                        //如果目标索引是一块别的拼图
                        if (targetIndexNew != selectedIndex) {
                            puzzleItemMove(selectedItem, targetIndexNew, puzzleItemSwitch);
                        } else {
                            //还原回原来的位置
                            puzzleItemMove(selectedItem, selectedIndex);
                            selectedItem = null;
                            relatedItem = null;
                        }
                    } else {
                        //如果鼠标在拼图之外的区域松开，则被拖动的拼图还原回原来的位置
                        puzzleItemMove(selectedItem, selectedIndex);
                        selectedItem = null;
                        relatedItem = null;
                        targetIndexNew = targetIndexOld = selectedIndex;
                    }
                }
            }

            //鼠标移动
            function mouseMoveHandler(event) {
                var mouseX = event.page.x,
                    mouseY = event.page.y;

                event.preventDefault();

                //设置选中元素的位置，跟随鼠标
                selectedItem.setPosition({
                    x: mouseX - disX - puzzlePosX,
                    y: mouseY - disY - puzzlePosY
                })

                //计算鼠标当前位置是否在拼图区域之内（拼图边缘也算在外）
                isInsidePuzzle = (function () {
                    if (mouseX <= puzzlePosX || mouseX - puzzlePosX >= puzzleWidth) {
                        return false;
                    }
                    if (mouseY <= puzzlePosY || mouseY - puzzlePosY >= puzzleHeght) {
                        return false;
                    }
                    return true;
                })();

                //如果鼠标当前位置在拼图区域之内，再做目标索引计算
                if (isInsidePuzzle) {
                    //race("[mouseMoveHandler]isInsidePuzzle = true");

                    //计算目标索引,xIndex和yIndex分别表示当前位置所处的列序号和行序号
                    var xIndex = Math.ceil((mouseX - puzzlePosX) / puzzleItemWidth),
                        yIndex = Math.ceil((mouseY - puzzlePosY) / puzzleItemHeght);
                    targetIndexNew = (yIndex - 1) * puzzleSizeX + xIndex;

                    if (targetIndexNew != targetIndexOld) {
                        isTargetIndexChanged = true;
                    }
                    //只有当目标索引发生改变时，才移动拼图做示意
                    if (isTargetIndexChanged) {
                        //如果上一个目标索引的拼图不是鼠标正在移动的这个，那么就需要恢复这张拼图的位置到它原来的地方
                        if (targetIndexOld != selectedIndex) {
                            var lastRelatedItemIndex = relatedItem.posIndex;
                            puzzleItemMove(relatedItem, lastRelatedItemIndex);
                        }

                        //更新相关元素，取得拼图数组中posIndex等于当前的目标索引的元素
                        relatedItem = puzzleArray.filter(function (item, index) {
                            return item.posIndex == targetIndexNew;
                        })[0];
                        //如果下一个目标索引，不是被拖走的拼图原来所在的位置，就移动新的目标索引的拼图到被拖走的拼图的位置
                        if (targetIndexNew != selectedIndex) {
                            puzzleItemMove(relatedItem, selectedIndex);
                        }

                        //重置目标索引改变的逻辑值
                        isTargetIndexChanged = false;

                        //更新上一个目标索引
                        targetIndexOld = targetIndexNew;
                    }
                } else {
                    //如果移到拼图区域之外，则考虑还原上一个目标索引的拼图
                    if (targetIndexOld != selectedIndex) {
                        var lastRelatedItemIndex = relatedItem.posIndex;
                        puzzleItemMove(relatedItem, lastRelatedItemIndex);
                    }
                    //还原targetIndexOld的值，以处理移到拼图外的情况。
                    targetIndexOld = selectedIndex;
                }
            }

            //每一次拼图交换的功能实现的函数，更改对应元素的posIndex，并更改zIndex
            function puzzleItemSwitch() {

                //交换元素的posIndex
                selectedItem.posIndex = targetIndexNew;
                relatedItem.posIndex = selectedIndex;

                //交换元素的zIndex，通过posIndex来赋值
                selectedItem.setStyle("zIndex", selectedItem.posIndex);
                relatedItem.setStyle("zIndex", relatedItem.posIndex);

                //清除对相关元素的引用
                selectedItem = null;
                relatedItem = null;

                //一次更换完成，计数器+1
                moveStepCount++;
                updateCount();

                //然后再判断拼图游戏是否完成
                clearJudgement();
            }

            //每一块拼图在游戏中的移动函数
            function puzzleItemMove(moveItem, moveToIndex, endFn) {
                var moveToX = validPosArrayX[moveToIndex - 1],
                    moveToY = validPosArrayY[moveToIndex - 1],
                    originZIndex = moveItem.posIndex;
                moveItemMorph = moveItem.get("morph");
                moveItemMorph.addEvent("start", moveStartHandler);
                moveItemMorph.addEvent("complete", moveEndHandler);
                moveItem.morph({
                    left: moveToX,
                    top: moveToY
                });

                function moveStartHandler() {
                    moveItem.setStyle("zIndex", 1000);
                }

                function moveEndHandler() {
                    moveItemMorph.removeEvent("start", moveStartHandler);
                    moveItemMorph.removeEvent("complete", moveEndHandler);
                    moveItem.setStyle("zIndex", originZIndex);

                    //结尾执行的函数，如果需要的话
                    if (typeOf(endFn) == "function") {
                        endFn();
                    }
                }
            }

            //完成拼图游戏的判定函数
            function clearJudgement() {
                //检查puzzleArray中的每一个元素的puzzleIndex和posIndex是否全部一致
                var isGameClear = puzzleArray.every(function (item, index) {
                    var puzzleIndex = item.retrieve("puzzleIndex");
                    return item.posIndex == puzzleIndex;
                });

                if (isGameClear) {
                    clearShow();
                }
            }

            //确定完成拼图游戏后，执行的函数
            function clearShow() {
                //清除所有事件侦听
                puzzle.removeEvent("mouseover", mouseOverHandler);
                puzzle.removeEvent("mouseout", mouseOutHandler);
                puzzle.removeEvent("mousedown", mouseDownHandler);
                puzzle.removeEvent("mouseup", mouseUpHandler);

                var clearAnimeFlag = null,
                    count = 0;

                //按顺序点亮所有拼图的动画
                var enterFrameHandler = function () {
                    var item = puzzleArray[count++];
                    item.fade(1);
                    if (count < puzzleNumber) {
                        clearAnimeFlag = setTimeout(enterFrameHandler, 50);
                    }
                };

                clearAnimeFlag = setTimeout(enterFrameHandler, 50);

                //游戏完成后的信息~❤
                puzzleNote.set('html', 'Congratulations ! Your final step count is <em class="step_count">' + moveStepCount + '</em>.');
            }
        }

//创建全局变量puzzleGame
        window.puzzleGame = {};

//添加方法到全局变量puzzleGame中
        puzzleGame.start = function () {
            puzzleConfigSet();
        };


    })(document.id);


    puzzleGame.start();

</script>
</html>
